﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 版权所有 (c) 2022 北京超维景生物科技有限公司 保留所有权利。
 * CLR版本：4.0.30319.42000
 * 文件名：Kernel
 * 
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/2/15 9:28:31
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ImageK.Gui;
using ImageK.Process;

namespace ImageK.Plugin.Filter
{
    public class PlugInFilterRunner
    {
        private string command;                 // the command, can be but need not be the name of the PlugInFilter
        private object theFilter;                   // the instance of the PlugInFilter
        private ImagePlus imp;
        private int flags;                          // the flags returned by the PlugInFilter
        private Overlay originalOverlay;        // overlay before pressing 'preview', to revert
        private bool previewCheckboxOn;          // the state of the preview checkbox (true = on)
        private bool bgPreviewOn;        // tells the background thread that preview is allowed
        private bool bgKeepPreview;      // tells the background thread to keep the result of preview
        private Thread previewThread;        // the thread of the preview
        // private GenericDialog gd;            // the dialog (if it registers here by setDialog)
        // private Checkbox previewCheckbox;    // reference to the preview Checkbox of the dialog
        private long previewTime;               // time (ms) needed for preview processing
        private bool ipChanged;          // whether the image data have been changed
        private int processedAsPreview;         // the slice processed during preview (if non-zero)
        private object snapshotPixels;      // the snapshot to show we have one and for undo in case of parallel actions intervening
        private Task[] slicesForThread;       // gives first&last slice that a given thread should process
        private Dictionary<Thread, ImageProcessor> roisForThread;// gives ROI that a given thread should process
        Hashtable sliceForThread = new Hashtable(); // here the stack slice currently processed is stored.
        private int nPasses;                        // the number of calls to the run(ip) method of the filter
        private int pass;                       // passes done so far
        private bool doStack;

		/** The constructor runs a PlugInFilter or ExtendedPlugInFilter by calling its
         * setup, run, etc. methods. For details, see the documentation of interfaces
         * PlugInFilter and ExtendedPlugInFilter.
         * @param theFilter The PlugInFilter to be run
         * @param command	The command that has caused running the PlugInFilter
         * @param arg		The argument specified for this PlugInFilter in IJ_Props.txt or in the
         * plugins.config file of a .jar archive conatining a collection of plugins. <code>arg</code>
         * may be a string of length zero.
         */
		public PlugInFilterRunner(object theFilter, string command, string arg)
		{
			this.theFilter = theFilter;
			this.command = command;
			imp = WindowManager.getCurrentImage();
			flags = ((PlugInFilter)theFilter).setup(arg, imp);  // S E T U P
			if ((flags & BasePlugInFilter.DONE) != 0) return;
			if (!checkImagePlus(imp, flags, command)) return;   // check whether the PlugInFilter can handle this image type
			if ((flags & BasePlugInFilter.NO_IMAGE_REQUIRED) != 0)
				imp = null;                                 // if the plugin does not want an image, it should not get one
			Roi roi = null;
			if (imp != null)
			{
				roi = imp.getRoi();
				if (roi != null) roi.endPaste();                // prepare the image: finish previous paste operation (if any)
				if (!imp.Lock())
				    return;                 // exit if image is in use
				nPasses = ((flags & BasePlugInFilter.CONVERT_TO_FLOAT) != 0) ? imp.getProcessor().getNChannels() : 1;
			}
			if (theFilter is ExtendedPlugInFilter) { // calling showDialog required?
				//todo:
				// try
				// {
				// 	flags = ((ExtendedPlugInFilter)theFilter).showDialog(imp, command, this);  // D I A L O G (may include preview)
				// }
				// catch (Exception e)
				// {
				// 	killPreview();
				// 	if (Macro.MACRO_CANCELED.equals(e.getMessage()))
				// 		throw new RuntimeException(Macro.MACRO_CANCELED);
				// }
				// if (snapshotPixels != null)
				// 	Undo.setup(Undo.FILTER, imp);           // ip has a snapshot that may be used for Undo
				// boolean keepPreviewFlag = (flags & ExtendedPlugInFilter.KEEP_PREVIEW) != 0;
				// if (keepPreviewFlag && imp != null && previewThread != null && ipChanged &&
				// 		previewCheckbox != null && previewCheckboxOn)
				// {
				// 	bgKeepPreview = true;
				// 	waitForPreviewDone();
				// 	processedAsPreview = imp.getCurrentSlice();
				// }
				// else
				// {
				// 	killPreview();
				// 	previewTime = 0;
				// }
			} // if ExtendedPlugInFilter
			if ((flags & BasePlugInFilter.DONE) != 0)
			{
				if (imp != null)
					imp.Unlock();
				return;
			}
			else if (imp == null)
			{
				((PlugInFilter)theFilter).run(null);        // not DONE, but NO_IMAGE_REQUIRED
				return;
			}
			/* preparing for the run(ip) method of the PlugInFilter... */
			int slices = imp.getStackSize();
			//IJ.log("processedAsPreview="+processedAsPreview+"; slices="+slices+"; doesStacks="+((flags&PlugInFilter.DOES_STACKS)!=0));
			if ((flags & BasePlugInFilter.PARALLELIZE_IMAGES) != 0)
				flags &= ~BasePlugInFilter.PARALLELIZE_STACKS;
			doStack = slices > 1 && (flags & BasePlugInFilter.DOES_STACKS) != 0;
			imp.startTiming();
			if (doStack || processedAsPreview == 0)
			{               // if processing during preview was not enough
							//IJ.showStatus(command + (doStack ? " (Stack)..." : "..."));
				ImageProcessor ip = imp.getProcessor();
				pass = 0;
				if (!doStack)
				{      
                    // single image
					FloatProcessor fp = null;
					prepareProcessor(ip, imp);
					announceSliceNumber(imp.getCurrentSlice());
					if (theFilter is ExtendedPlugInFilter)
					    ((ExtendedPlugInFilter)theFilter).setNPasses(nPasses);
					if ((flags & BasePlugInFilter.NO_CHANGES) == 0)
					{   // for filters modifying the image
						bool disableUndo = Prefs.disableUndo || (flags & BasePlugInFilter.NO_UNDO) != 0;
						if (!disableUndo || ((ip is ColorProcessor)&& WindowManager.getWindow("B&C") != null)) {
							ip.snapshot();
							snapshotPixels = ip.getSnapshotPixels();
						}
					}
					processOneImage(ip, fp, snapshotPixels);    // may also set class variable snapshotPixels
					if ((flags & BasePlugInFilter.NO_CHANGES) == 0)
					{   // (filters doing no modifications don't change undo status)
						if (snapshotPixels != null)
						{
							ip.setSnapshotPixels(snapshotPixels);
							Undo.setup(Undo.FILTER, imp);
						}
						else
							Undo.reset();
					}
					if ((flags & BasePlugInFilter.NO_CHANGES) == 0 && (flags & BasePlugInFilter.KEEP_THRESHOLD) == 0)
						ip.resetBinaryThreshold();
				}
				else
				{  // stack
					if ((flags & BasePlugInFilter.NO_UNDO_RESET) == 0)
						Undo.reset();    // no undo for processing a complete stack
					IJ.resetEscape();
					int slicesToDo = processedAsPreview != 0 ? slices - 1 : slices;
					nPasses *= slicesToDo;
                    if (theFilter is ExtendedPlugInFilter)
                    {
						//todo:
                        // ((ExtendedPlugInFilter)theFilter).setNPasses(nPasses);
					}
					int threads = 1;
					if ((flags & BasePlugInFilter.PARALLELIZE_STACKS) != 0)
					{
						threads = Prefs.getThreads(); // multithread support for multiprocessor machines
						if (threads > slicesToDo) threads = slicesToDo;
						if (threads > 1) slicesForThread = new Task[threads];
					}
					int startSlice = 1;
                    if (threads > 1)
                    {
                        for (int i = 1; i < threads; i++)
                        {               // setup the background threads
                            int endSlice = (slicesToDo * i) / threads;
                            if (processedAsPreview != 0 && processedAsPreview <= endSlice) endSlice++;
                            // Thread bgThread = new Thread(Run)
                            //                   {
                            // 	Name = command + " " + startSlice + "-" + endSlice
                            // };
                            // slicesForThread.Add(bgThread, new int[] { startSlice, endSlice });
                            slicesForThread[i] = Task.Factory.StartNew(() =>
                            {
                                processStack(startSlice, endSlice);
                            });
                            // bgThread.Start();
                            //IJ.log("Stack: Thread for slices "+startSlice+"-"+endSlice+" started");
                            startSlice = endSlice + 1;
                        }
                        Task.WaitAll(slicesForThread);
					}

					//IJ.log("Stack: Slices "+startSlice+"-"+slices+" by main thread");
					processStack(startSlice, slices);           // the current thread does the rest
					// if (slicesForThread != null)
					// {
					// 	while (slicesForThread.Count > 0)
					// 	{     // for all other threads:
					// 		Thread theThread = (Thread)slicesForThread.keys().nextElement();
					// 		try
					// 		{
					// 			theThread.Join();               // wait until thread has finished
					// 		}
					// 		catch (Exception e) { }
					// 		slicesForThread.Remove(theThread);  // and remove it from the list.
					// 	}
					// }

                    
                }
			} // end processing:
			if ((flags & BasePlugInFilter.FINAL_PROCESSING) != 0 && !IJ.escapePressed())
				((PlugInFilter)theFilter).setup("final", imp);
			if (IJ.escapePressed())
			{
				IJ.ShowStatus(command + " INTERRUPTED");
				IJ.showProgress(1, 1);
			}
			else
				IJ.ShowTime(imp, imp.getStartTime() - previewTime, command + ": ", doStack ? slices : 1);
			IJ.showProgress(1.0);
			if (ipChanged)
			{
				imp.changes = true;
				imp.updateAndDraw();
			}
			ImageWindow win = imp.getWindow();
			if (win != null)
			{
				win.running = false;
				win.running2 = false;
			}
			imp.Unlock();
		}

        /** Process a stack or part of it. The slice given by class variable
         *	processedAsPreview remains unchanged.
         * @param firstSlice Slice number of the first slice to be processed
         * @param endSlice	 Slice number of the last slice to be processed
         */
        private void processStack(int firstSlice, int endSlice)
        {
            ImageStack stack = imp.getStack();
            ImageProcessor ip = stack.getProcessor(firstSlice);
            prepareProcessor(ip, imp);
            ip.setLineWidth(Line.getWidth());       //in contrast to imp.getProcessor, stack.getProcessor does not do this
            FloatProcessor fp = null;
            int slices = imp.getNSlices();
            for (int i = firstSlice; i <= endSlice; i++)
            {
                if (i != processedAsPreview)
                {
                    announceSliceNumber(i);
                    ip.setPixels(stack.getPixels(i));
                    ip.setSliceNumber(i);
                    ip.setSnapshotPixels(null);
                    processOneImage(ip, fp, null);
                    if (IJ.escapePressed()) { IJ.beep(); break; }
                }
            }
        }

        /** prepare an ImageProcessor by setting roi and CalibrationTable.
 */
        private void prepareProcessor(ImageProcessor ip, ImagePlus imp)
        {
            ImageProcessor mask = imp.getMask();
            Roi roi = imp.getRoi();
            if (roi != null && roi.isArea())
                ip.setRoi(roi);
            else
            {
                Roi nullRoi = null;
                ip.setRoi(nullRoi);
            }

            if (imp.getStackSize() > 1)
            {
                ImageProcessor ip2 = imp.getProcessor();
                double min1 = ip2.getMinThreshold();
                double max1 = ip2.getMaxThreshold();
                double min2 = ip.getMinThreshold();
                double max2 = ip.getMaxThreshold();
                if (min1 != ImageProcessor.NO_THRESHOLD && (min1 != min2 || max1 != max2))
                    ip.setThreshold(min1, max1, ImageProcessor.NO_LUT_UPDATE);
            }
        }

		/**
         * Process a single image with the PlugInFilter.
         * @param ip		The image data that should be processed
         * @param fp		A Floatprocessor as a target for conversion to Float. May be null.
         * @param snapshotPixels Valid snapshotPixels for the current ImageProcessor (null when processing a stack)
         * Class variables used: flags (input), snapshotPixels (set if a snapshot of ip
         * is taken), ipChanged (set if ip was probably changed).
         */
		private void processOneImage(ImageProcessor ip, FloatProcessor fp, object snapshotPixels)
		{
			if ((flags & BasePlugInFilter.PARALLELIZE_IMAGES) != 0)
			{
				// processImageUsingThreads(ip, fp, snapshotPixels);
				return;
			}
			// Thread thread = Thread.currentThread();
			bool convertToFloat = (flags & BasePlugInFilter.CONVERT_TO_FLOAT) != 0 && !(ip is FloatProcessor);
			// bool doMasking = (flags & BasePlugInFilter.SUPPORTS_MASKING) != 0 && ip.getMask() != null;
			// if (snapshotPixels == null && (doMasking || ((flags & BasePlugInFilter.SNAPSHOT) != 0) && !convertToFloat))
			// {
			// 	ip.snapshot();
			// 	this.snapshotPixels = ip.getSnapshotPixels();
			// }
			if (convertToFloat)
			{
				//todo:
				// for (int i = 0; i < ip.getNChannels(); i++)
				// {
				// 	fp = ip.toFloat(i, fp);
				// 	fp.setSliceNumber(ip.getSliceNumber());
				// 	if (thread.isInterrupted()) return;         // interrupt processing for preview?
				// 	if ((flags & PlugInFilter.SNAPSHOT) != 0) fp.snapshot();
				// 	if (doStack) IJ.showProgress(pass / (double)nPasses);
				// 	((PlugInFilter)theFilter).run(fp);
				// 	if (thread.isInterrupted()) return;
				// 	//IJ.log("slice="+getSliceNumber()+" pass="+pass+"/"+nPasses);
				// 	pass++;
				// 	if ((flags & PlugInFilter.NO_CHANGES) == 0)
				// 	{
				// 		ipChanged = true;
				// 		ip.setPixels(i, fp);
				// 	}
				// }
			}
			else
			{
				if ((flags & BasePlugInFilter.NO_CHANGES) == 0) ipChanged = true;
				if (doStack) IJ.showProgress(pass / (double)nPasses);
				((PlugInFilter)theFilter).run(ip);
				pass++;
			}

			// if (thread.isInterrupted()) return;
			// if (doMasking)
			{
				// if (snapshotPixels != null)
				// 	ip.setSnapshotPixels(snapshotPixels); //in case something has intervened and destroyed the snapshot
				// ip.reset(ip.getMask());  //restore image outside irregular roi
			}
		}

		/** Make the slice number accessible to the PlugInFilter by putting it
         * into the appropriate hashtable.
         */
		private void announceSliceNumber(int slice)
        {
            lock(sliceForThread){
                int number = slice;
                sliceForThread[Thread.CurrentThread] = number;
            }
        }

		/** test whether an ImagePlus can be processed based on the flags specified
         *	and display an error message if not.
         */
		private bool checkImagePlus(ImagePlus imp, int flags, string cmd)
        {
            bool imageRequired = (flags & BasePlugInFilter.NO_IMAGE_REQUIRED) == 0;
            if (imageRequired && imp == null)
            {
                IJ.noImage(); 
                return false;
            }
            if (imageRequired)
            {
                if (imp.getProcessor() == null)
                { wrongType(flags, cmd); return false; }
                int type = imp.getType();
                switch (type)
                {
                    case ImagePlus.GRAY8:
                        if ((flags & BasePlugInFilter.DOES_8G) == 0)
                        { wrongType(flags, cmd); return false; }
                        break;
                    case ImagePlus.COLOR_256:
                        if ((flags & BasePlugInFilter.DOES_8C) == 0)
                        { wrongType(flags, cmd); return false; }
                        break;
                    case ImagePlus.GRAY16:
                        if ((flags & BasePlugInFilter.DOES_16) == 0)
                        { wrongType(flags, cmd); return false; }
                        break;
                    case ImagePlus.GRAY32:
                        if ((flags & BasePlugInFilter.DOES_32) == 0)
                        { wrongType(flags, cmd); return false; }
                        break;
                    case ImagePlus.COLOR_RGB:
                        if ((flags & BasePlugInFilter.DOES_RGB) == 0)
                        { wrongType(flags, cmd); return false; }
                        break;
                }
                if ((flags & BasePlugInFilter.ROI_REQUIRED) != 0 && imp.getRoi() == null)
                { IJ.error(cmd, "This command requires a selection"); return false; }
                if ((flags & BasePlugInFilter.STACK_REQUIRED) != 0 && imp.getStackSize() == 1)
                { IJ.error(cmd, "This command requires a stack"); return false; }
            } // if imageRequired
            return true;
        }

        /** Display an error message, telling the allowed image types
        */
        static void wrongType(int flags, string cmd)
        {
            string s = "\"" + cmd + "\" requires an image of type:\n \n";
            if ((flags & BasePlugInFilter.DOES_8G) != 0) s += "	8-bit grayscale\n";
            if ((flags & BasePlugInFilter.DOES_8C) != 0) s += "	8-bit color\n";
            if ((flags & BasePlugInFilter.DOES_16) != 0) s += "	16-bit grayscale\n";
            if ((flags & BasePlugInFilter.DOES_32) != 0) s += "	32-bit (float) grayscale\n";
            if ((flags & BasePlugInFilter.DOES_RGB) != 0) s += "	RGB color\n";
            IJ.error(s);
        }
	}
}
